<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue';

const canvasRef = ref<any>(null);

// 栅格配置
const gridSize = ref(20);
const snapToGrid = ref(true);
const snapThreshold = ref(10);

// 组件数据
const components = reactive<any[]>([
  { id: 1, name: '图表1', x: 100, y: 100, width: 200, height: 150 },
  { id: 2, name: '图表2', x: 350, y: 200, width: 180, height: 120 },
  { id: 3, name: '图表3', x: 200, y: 350, width: 220, height: 160 },
]);

// 当前选中的组件
const selectedId = ref<any>(null);

// 拖拽状态
const isDragging = ref(false);
const isSnapping = ref(false);
const dragStartPos = ref<any>({ x: 0, y: 0 });
const dragStartItemPos = ref<any>({ x: 0, y: 0 });
const currentDragItem = ref<any>(null);

// 吸附辅助线
const snapLines = ref<any>({ vertical: null, horizontal: null });
const showSnapLines = computed(() => snapLines.value.vertical !== null || snapLines.value.horizontal !== null);

// 栅格背景样式
const gridBackgroundStyle = computed(() => ({
  backgroundImage: `
    linear-gradient(to right, #e0e0e0 1px, transparent 1px),
    linear-gradient(to bottom, #e0e0e0 1px, transparent 1px)
  `,
  backgroundSize: `${gridSize.value}px ${gridSize.value}px`,
}));

// 监听栅格大小变化，更新背景
watch(gridSize, () => {
  const gridBg = document.querySelector('.grid-background');
  if (gridBg) {
    Object.assign((gridBg as any).style, gridBackgroundStyle.value);
  }
}, { immediate: true });

// 计算最近的栅格点
function snapToGridPoint(x: any, y: any) {
  if (!snapToGrid.value) {
    return { x, y };
  }

  const snappedX = Math.round(x / gridSize.value) * gridSize.value;
  const snappedY = Math.round(y / gridSize.value) * gridSize.value;

  return { x: snappedX, y: snappedY };
}

// 检查是否应该吸附
function shouldSnap(currentX: any, currentY: any, targetX: any, targetY: any) {
  const distanceX = Math.abs(currentX - targetX);
  const distanceY = Math.abs(currentY - targetY);
  return distanceX <= snapThreshold.value && distanceY <= snapThreshold.value;
}

// 选中组件
function selectItem(id: any) {
  selectedId.value = id;
}

// 点击画布空白处取消选中
function onCanvasMouseDown(e: any) {
  if (e.target.classList.contains('canvas-container')
    || e.target.classList.contains('canvas')
    || e.target.classList.contains('grid-background')) {
    selectedId.value = null;
  }
}

// 开始拖拽组件
function onItemMouseDown(e: any, item: any) {
  e.stopPropagation();

  // 选中当前组件
  selectedId.value = item.id;

  // 设置拖拽状态
  isDragging.value = true;
  currentDragItem.value = item;

  // 记录起始位置
  dragStartPos.value = { x: e.clientX, y: e.clientY };
  dragStartItemPos.value = { x: item.x, y: item.y };

  // 绑定全局事件
  document.addEventListener('mousemove', onMouseMove);
  document.addEventListener('mouseup', onMouseUp);
}

// 拖拽移动
function onMouseMove(e: any) {
  if (!isDragging.value || !currentDragItem.value) {
    return;
  }

  // 计算偏移量
  const deltaX = e.clientX - dragStartPos.value.x;
  const deltaY = e.clientY - dragStartPos.value.y;

  // 计算新位置
  let newX = dragStartItemPos.value.x + deltaX;
  let newY = dragStartItemPos.value.y + deltaY;

  // 边界检测
  newX = Math.max(0, newX);
  newY = Math.max(0, newY);

  // 栅格吸附
  if (snapToGrid.value) {
    const snapped = snapToGridPoint(newX, newY);

    // 检查是否应该吸附
    if (shouldSnap(newX, newY, snapped.x, snapped.y)) {
      isSnapping.value = true;
      snapLines.value = {
        vertical: snapped.x,
        horizontal: snapped.y,
      };
      newX = snapped.x;
      newY = snapped.y;
    } else {
      isSnapping.value = false;
      snapLines.value = { vertical: null, horizontal: null };
    }
  } else {
    isSnapping.value = false;
    snapLines.value = { vertical: null, horizontal: null };
  }

  // 更新组件位置
  currentDragItem.value.x = newX;
  currentDragItem.value.y = newY;
}

// 结束拖拽
function onMouseUp() {
  isDragging.value = false;
  isSnapping.value = false;
  currentDragItem.value = null;
  snapLines.value = { vertical: null, horizontal: null };

  // 移除全局事件
  document.removeEventListener('mousemove', onMouseMove);
  document.removeEventListener('mouseup', onMouseUp);
}
</script>

<template>
  <div class="canvas-container" @mousedown="onCanvasMouseDown">
    <!-- 画布区域 -->
    <div ref="canvasRef" class="canvas">
      <!-- 栅格背景 -->
      <div class="grid-background" />

      <!-- 遍历所有组件 -->
      <div
        v-for="(item, index) of components"
        :key="item.id"
        class="draggable-item"
        :class="{ selected: item.id === selectedId, snapping: isSnapping && currentDragItem?.id === item.id }"
        :style="{
          left: `${item.x}px`,
          top: `${item.y}px`,
          width: `${item.width}px`,
          height: `${item.height}px`,
          zIndex: index + 1,
        }"
        @mousedown="onItemMouseDown($event, item)"
        @click="selectItem(item.id)"
      >
        <!-- 这里放实际内容，比如图表 -->
        <div class="item-content">
          {{ item.name }}
          <div class="position-info">
            {{ item.x }}, {{ item.y }}
          </div>
        </div>

        <!-- 选中时显示边框 -->
        <div v-if="item.id === selectedId" class="selection-border" />
      </div>

      <!-- 吸附辅助线 -->
      <div v-if="showSnapLines" class="snap-lines">
        <div
          v-if="snapLines.vertical !== null"
          class="snap-line vertical"
          :style="{ left: `${snapLines.vertical}px` }"
        />
        <div
          v-if="snapLines.horizontal !== null"
          class="snap-line horizontal"
          :style="{ top: `${snapLines.horizontal}px` }"
        />
      </div>
    </div>

    <!-- 控制面板 -->
    <div class="control-panel">
      <label>
        <input v-model="snapToGrid" type="checkbox" />
        栅格吸附 ({{ gridSize }}px)
      </label>
      <label>
        栅格大小:
        <input v-model="gridSize" type="range" min="10" max="50" step="5" />
        {{ gridSize }}px
      </label>
      <label>
        吸附距离:
        <input v-model="snapThreshold" type="range" min="5" max="30" step="5" />
        {{ snapThreshold }}px
      </label>
    </div>
  </div>
</template>

<style scoped>
.canvas-container {
  width: 100vw;
  height: 100vh;
  background: #f5f5f5;
  position: relative;
  overflow: hidden;
}

.canvas {
  width: 100%;
  height: 100%;
  position: relative;
  background: white;
}

.grid-background {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  background-image:
    linear-gradient(to right, #e0e0e0 1px, transparent 1px),
    linear-gradient(to bottom, #e0e0e0 1px, transparent 1px);
  background-size: 20px 20px;
}

.draggable-item {
  position: absolute;
  cursor: move;
  user-select: none;
  border: 2px solid transparent;
  border-radius: 4px;
  transition: border-color 0.2s, box-shadow 0.2s;
}

.draggable-item:hover {
  border-color: #409eff;
}

.draggable-item.selected {
  border-color: #409eff;
}

.draggable-item.snapping {
  box-shadow: 0 0 10px rgba(64, 158, 255, 0.5);
  border-color: #67c23a;
}

.item-content {
  width: 100%;
  height: 100%;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-weight: bold;
  border-radius: 2px;
  position: relative;
}

.position-info {
  position: absolute;
  bottom: 5px;
  right: 5px;
  font-size: 10px;
  opacity: 0.8;
}

.selection-border {
  position: absolute;
  top: -2px;
  left: -2px;
  right: -2px;
  bottom: -2px;
  border: 2px solid #409eff;
  border-radius: 4px;
  pointer-events: none;
}

.snap-lines {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 1000;
}

.snap-line {
  position: absolute;
  background: #67c23a;
  opacity: 0.8;
}

.snap-line.vertical {
  width: 1px;
  height: 100%;
  top: 0;
}

.snap-line.horizontal {
  width: 100%;
  height: 1px;
  left: 0;
}

.control-panel {
  position: absolute;
  top: 20px;
  right: 20px;
  background: white;
  padding: 15px;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 200px;
}

.control-panel label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
}

.control-panel input[type="range"] {
  flex: 1;
}

.control-panel input[type="checkbox"] {
  margin: 0;
}
</style>
